// Package repository
// Created by GoLand
// @User: lenora
// @Date: 2024/7/3
// @Time: 17:44

package repository

import (
	"camp/microbook/interactive/domain"
	"camp/microbook/interactive/repository/cache"
	"camp/microbook/interactive/repository/dao"
	"camp/microbook/pkg/logger"
	"context"
	"errors"
	"github.com/ecodeclub/ekit/slice"
	"golang.org/x/sync/errgroup"
)

type InteractiveRepository interface {
	IncrReadCnt(ctx context.Context, biz string, bizId uint64) error
	IncrLikeCnt(ctx context.Context, biz string, id, userId uint64) error
	DecrLikeCnt(ctx context.Context, biz string, id, userId uint64) error
	IncrCollectCnt(ctx context.Context, biz string, id, cid, userId uint64) error
	Get(ctx context.Context, biz string, id, userId uint64) (domain.Interactive, error)
	GetByIds(ctx context.Context, biz string, ids []uint64) ([]domain.Interactive, error)
}

func NewInteractiveRepository(dao dao.InteractiveDAO, redisCache cache.InteractiveCache) InteractiveRepository {
	return &interactiveRepository{
		dao:   dao,
		cache: redisCache,
	}
}

type interactiveRepository struct {
	dao   dao.InteractiveDAO
	cache cache.InteractiveCache
	l     logger.Logger
}

func (repo *interactiveRepository) GetByIds(ctx context.Context, biz string, ids []uint64) ([]domain.Interactive, error) {
	res, err := repo.dao.GetByIds(ctx, biz, ids)
	if err != nil {
		return nil, err
	}
	return slice.Map[dao.Interactive, domain.Interactive](res, func(idx int, src dao.Interactive) domain.Interactive {
		return repo.toDomain(src)
	}), nil
}

func (repo *interactiveRepository) Get(ctx context.Context, biz string, id, userId uint64) (domain.Interactive, error) {

	var (
		eg  errgroup.Group
		res = domain.Interactive{BizId: id}
	)
	//获取总数
	eg.Go(func() error {
		var cErr error
		data, cErr := repo.cache.Get(ctx, biz, id)
		if cErr == nil {
			res.CollectCnt = data.CollectCnt
			res.ReadCnt = data.ReadCnt
			res.LikeCnt = data.LikeCnt
			return nil
		} else {
			intr, dErr := repo.dao.Get(ctx, biz, id)
			switch dErr {
			case nil:
				data = repo.toDomain(intr)
				if er := repo.cache.Set(ctx, biz, id, data); er != nil {
					repo.l.Error("sync interactive data to cache failed", logger.Error(er))
				}
				res.CollectCnt = data.CollectCnt
				res.ReadCnt = data.ReadCnt
				res.LikeCnt = data.LikeCnt
				return nil
			case dao.ErrRecordNotFound:
				res.CollectCnt = 0
				res.ReadCnt = 0
				res.LikeCnt = 0
				return nil
			default:
				return dErr
			}
		}
	})
	//获取点赞状态
	eg.Go(func() error {
		res.Liked = repo.UserLiked(ctx, biz, id, userId)
		return nil
	})
	//获取收藏状态
	eg.Go(func() error {
		res.Collected = repo.UserCollected(ctx, biz, id, userId)
		return nil
	})
	err := eg.Wait()
	return res, err
}

func (repo *interactiveRepository) IncrCollectCnt(ctx context.Context, biz string, id, cid, userId uint64) error {
	err := repo.dao.InsertCollect(ctx, biz, id, cid, userId)
	if err != nil {
		return err
	}

	//db与cache的数据不一致
	return repo.cache.IncrCollectCntIfPresent(ctx, biz, id)
}

func (repo *interactiveRepository) IncrLikeCnt(ctx context.Context, biz string, id, userId uint64) error {
	err := repo.dao.UpsertLike(ctx, biz, id, userId)
	if err != nil {
		return err
	}

	//db与cache的数据不一致
	err = repo.cache.IncrLikeCntIfPresent(ctx, biz, id)
	if err != nil {
		return err
	}
	//更新点赞排行榜
	err = repo.cache.IncrRankingIfPresent(ctx, biz, id)
	if errors.Is(err, cache.ErrElementNotExist) {
		res, errs := repo.dao.Get(ctx, biz, id)
		if errs != nil {
			return err
		}
		err = repo.cache.SetRankingItem(ctx, biz, id, res.LikeCnt)
	}
	return err
}

func (repo *interactiveRepository) DecrLikeCnt(ctx context.Context, biz string, id, userId uint64) error {
	err := repo.dao.RemoveLike(ctx, biz, id, userId)
	if err != nil {
		return err
	}

	//db与cache的数据不一致
	return repo.cache.DecrLikeCntIfPresent(ctx, biz, id)
}

func (repo *interactiveRepository) IncrReadCnt(ctx context.Context, biz string, bizId uint64) error {
	err := repo.dao.IncrReadCnt(ctx, biz, bizId)
	if err != nil {
		return err
	}

	//db与cache的数据不一致
	return repo.cache.IncrReadCntIfPresent(ctx, biz, bizId)
}

func (repo *interactiveRepository) UserLiked(ctx context.Context, biz string, id, userId uint64) bool {
	_, err := repo.dao.GetLikeInfo(ctx, biz, id, userId)
	switch err {
	case nil:
		return true
	case dao.ErrRecordNotFound:
		return false
	default:
		repo.l.Error("get user like info failed",
			logger.Error(err),
		)
		return false
	}
}
func (repo *interactiveRepository) UserCollected(ctx context.Context, biz string, id, userId uint64) bool {
	_, err := repo.dao.GetCollectInfo(ctx, biz, id, userId)
	switch err {
	case nil:
		return true
	case dao.ErrRecordNotFound:
		return false
	default:
		repo.l.Error("get user collect info failed",
			logger.Error(err),
		)
		return false
	}
}

func (repo *interactiveRepository) toDomain(intr dao.Interactive) domain.Interactive {
	return domain.Interactive{
		BizId:      intr.BizId,
		ReadCnt:    intr.ReadCnt,
		LikeCnt:    intr.LikeCnt,
		CollectCnt: intr.CollectCnt,
	}
}
